/***
 * ASM: a very small and fast Java bytecode manipulation framework Copyright (c) 2000-2011 INRIA,
 * France Telecom All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met: 1. Redistributions of source code must retain the
 * above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions
 * in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.googlecode.aviator.asm.commons;

import com.googlecode.aviator.asm.Handle;
import com.googlecode.aviator.asm.Label;
import com.googlecode.aviator.asm.MethodVisitor;
import com.googlecode.aviator.asm.Opcodes;
import com.googlecode.aviator.asm.Type;

/**
 * A {@link MethodVisitor} providing a more detailed API to generate and transform instructions.
 *
 * @author Eric Bruneton
 */
public class InstructionAdapter extends MethodVisitor {

  public final static Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;");

  /**
   * Creates a new {@link InstructionAdapter}. <i>Subclasses must not use this constructor</i>.
   * Instead, they must use the {@link #InstructionAdapter(int, MethodVisitor)} version.
   *
   * @param mv the method visitor to which this adapter delegates calls.
   */
  public InstructionAdapter(final MethodVisitor mv) {
    this(Opcodes.ASM4, mv);
  }

  /**
   * Creates a new {@link InstructionAdapter}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of
   *        {@link Opcodes#ASM4}.
   * @param mv the method visitor to which this adapter delegates calls.
   */
  protected InstructionAdapter(final int api, final MethodVisitor mv) {
    super(api, mv);
  }

  @Override
  public void visitInsn(final int opcode) {
    switch (opcode) {
      case Opcodes.NOP:
        nop();
        break;
      case Opcodes.ACONST_NULL:
        aconst(null);
        break;
      case Opcodes.ICONST_M1:
      case Opcodes.ICONST_0:
      case Opcodes.ICONST_1:
      case Opcodes.ICONST_2:
      case Opcodes.ICONST_3:
      case Opcodes.ICONST_4:
      case Opcodes.ICONST_5:
        iconst(opcode - Opcodes.ICONST_0);
        break;
      case Opcodes.LCONST_0:
      case Opcodes.LCONST_1:
        lconst(opcode - Opcodes.LCONST_0);
        break;
      case Opcodes.FCONST_0:
      case Opcodes.FCONST_1:
      case Opcodes.FCONST_2:
        fconst(opcode - Opcodes.FCONST_0);
        break;
      case Opcodes.DCONST_0:
      case Opcodes.DCONST_1:
        dconst(opcode - Opcodes.DCONST_0);
        break;
      case Opcodes.IALOAD:
        aload(Type.INT_TYPE);
        break;
      case Opcodes.LALOAD:
        aload(Type.LONG_TYPE);
        break;
      case Opcodes.FALOAD:
        aload(Type.FLOAT_TYPE);
        break;
      case Opcodes.DALOAD:
        aload(Type.DOUBLE_TYPE);
        break;
      case Opcodes.AALOAD:
        aload(OBJECT_TYPE);
        break;
      case Opcodes.BALOAD:
        aload(Type.BYTE_TYPE);
        break;
      case Opcodes.CALOAD:
        aload(Type.CHAR_TYPE);
        break;
      case Opcodes.SALOAD:
        aload(Type.SHORT_TYPE);
        break;
      case Opcodes.IASTORE:
        astore(Type.INT_TYPE);
        break;
      case Opcodes.LASTORE:
        astore(Type.LONG_TYPE);
        break;
      case Opcodes.FASTORE:
        astore(Type.FLOAT_TYPE);
        break;
      case Opcodes.DASTORE:
        astore(Type.DOUBLE_TYPE);
        break;
      case Opcodes.AASTORE:
        astore(OBJECT_TYPE);
        break;
      case Opcodes.BASTORE:
        astore(Type.BYTE_TYPE);
        break;
      case Opcodes.CASTORE:
        astore(Type.CHAR_TYPE);
        break;
      case Opcodes.SASTORE:
        astore(Type.SHORT_TYPE);
        break;
      case Opcodes.POP:
        pop();
        break;
      case Opcodes.POP2:
        pop2();
        break;
      case Opcodes.DUP:
        dup();
        break;
      case Opcodes.DUP_X1:
        dupX1();
        break;
      case Opcodes.DUP_X2:
        dupX2();
        break;
      case Opcodes.DUP2:
        dup2();
        break;
      case Opcodes.DUP2_X1:
        dup2X1();
        break;
      case Opcodes.DUP2_X2:
        dup2X2();
        break;
      case Opcodes.SWAP:
        swap();
        break;
      case Opcodes.IADD:
        add(Type.INT_TYPE);
        break;
      case Opcodes.LADD:
        add(Type.LONG_TYPE);
        break;
      case Opcodes.FADD:
        add(Type.FLOAT_TYPE);
        break;
      case Opcodes.DADD:
        add(Type.DOUBLE_TYPE);
        break;
      case Opcodes.ISUB:
        sub(Type.INT_TYPE);
        break;
      case Opcodes.LSUB:
        sub(Type.LONG_TYPE);
        break;
      case Opcodes.FSUB:
        sub(Type.FLOAT_TYPE);
        break;
      case Opcodes.DSUB:
        sub(Type.DOUBLE_TYPE);
        break;
      case Opcodes.IMUL:
        mul(Type.INT_TYPE);
        break;
      case Opcodes.LMUL:
        mul(Type.LONG_TYPE);
        break;
      case Opcodes.FMUL:
        mul(Type.FLOAT_TYPE);
        break;
      case Opcodes.DMUL:
        mul(Type.DOUBLE_TYPE);
        break;
      case Opcodes.IDIV:
        div(Type.INT_TYPE);
        break;
      case Opcodes.LDIV:
        div(Type.LONG_TYPE);
        break;
      case Opcodes.FDIV:
        div(Type.FLOAT_TYPE);
        break;
      case Opcodes.DDIV:
        div(Type.DOUBLE_TYPE);
        break;
      case Opcodes.IREM:
        rem(Type.INT_TYPE);
        break;
      case Opcodes.LREM:
        rem(Type.LONG_TYPE);
        break;
      case Opcodes.FREM:
        rem(Type.FLOAT_TYPE);
        break;
      case Opcodes.DREM:
        rem(Type.DOUBLE_TYPE);
        break;
      case Opcodes.INEG:
        neg(Type.INT_TYPE);
        break;
      case Opcodes.LNEG:
        neg(Type.LONG_TYPE);
        break;
      case Opcodes.FNEG:
        neg(Type.FLOAT_TYPE);
        break;
      case Opcodes.DNEG:
        neg(Type.DOUBLE_TYPE);
        break;
      case Opcodes.ISHL:
        shl(Type.INT_TYPE);
        break;
      case Opcodes.LSHL:
        shl(Type.LONG_TYPE);
        break;
      case Opcodes.ISHR:
        shr(Type.INT_TYPE);
        break;
      case Opcodes.LSHR:
        shr(Type.LONG_TYPE);
        break;
      case Opcodes.IUSHR:
        ushr(Type.INT_TYPE);
        break;
      case Opcodes.LUSHR:
        ushr(Type.LONG_TYPE);
        break;
      case Opcodes.IAND:
        and(Type.INT_TYPE);
        break;
      case Opcodes.LAND:
        and(Type.LONG_TYPE);
        break;
      case Opcodes.IOR:
        or(Type.INT_TYPE);
        break;
      case Opcodes.LOR:
        or(Type.LONG_TYPE);
        break;
      case Opcodes.IXOR:
        xor(Type.INT_TYPE);
        break;
      case Opcodes.LXOR:
        xor(Type.LONG_TYPE);
        break;
      case Opcodes.I2L:
        cast(Type.INT_TYPE, Type.LONG_TYPE);
        break;
      case Opcodes.I2F:
        cast(Type.INT_TYPE, Type.FLOAT_TYPE);
        break;
      case Opcodes.I2D:
        cast(Type.INT_TYPE, Type.DOUBLE_TYPE);
        break;
      case Opcodes.L2I:
        cast(Type.LONG_TYPE, Type.INT_TYPE);
        break;
      case Opcodes.L2F:
        cast(Type.LONG_TYPE, Type.FLOAT_TYPE);
        break;
      case Opcodes.L2D:
        cast(Type.LONG_TYPE, Type.DOUBLE_TYPE);
        break;
      case Opcodes.F2I:
        cast(Type.FLOAT_TYPE, Type.INT_TYPE);
        break;
      case Opcodes.F2L:
        cast(Type.FLOAT_TYPE, Type.LONG_TYPE);
        break;
      case Opcodes.F2D:
        cast(Type.FLOAT_TYPE, Type.DOUBLE_TYPE);
        break;
      case Opcodes.D2I:
        cast(Type.DOUBLE_TYPE, Type.INT_TYPE);
        break;
      case Opcodes.D2L:
        cast(Type.DOUBLE_TYPE, Type.LONG_TYPE);
        break;
      case Opcodes.D2F:
        cast(Type.DOUBLE_TYPE, Type.FLOAT_TYPE);
        break;
      case Opcodes.I2B:
        cast(Type.INT_TYPE, Type.BYTE_TYPE);
        break;
      case Opcodes.I2C:
        cast(Type.INT_TYPE, Type.CHAR_TYPE);
        break;
      case Opcodes.I2S:
        cast(Type.INT_TYPE, Type.SHORT_TYPE);
        break;
      case Opcodes.LCMP:
        lcmp();
        break;
      case Opcodes.FCMPL:
        cmpl(Type.FLOAT_TYPE);
        break;
      case Opcodes.FCMPG:
        cmpg(Type.FLOAT_TYPE);
        break;
      case Opcodes.DCMPL:
        cmpl(Type.DOUBLE_TYPE);
        break;
      case Opcodes.DCMPG:
        cmpg(Type.DOUBLE_TYPE);
        break;
      case Opcodes.IRETURN:
        areturn(Type.INT_TYPE);
        break;
      case Opcodes.LRETURN:
        areturn(Type.LONG_TYPE);
        break;
      case Opcodes.FRETURN:
        areturn(Type.FLOAT_TYPE);
        break;
      case Opcodes.DRETURN:
        areturn(Type.DOUBLE_TYPE);
        break;
      case Opcodes.ARETURN:
        areturn(OBJECT_TYPE);
        break;
      case Opcodes.RETURN:
        areturn(Type.VOID_TYPE);
        break;
      case Opcodes.ARRAYLENGTH:
        arraylength();
        break;
      case Opcodes.ATHROW:
        athrow();
        break;
      case Opcodes.MONITORENTER:
        monitorenter();
        break;
      case Opcodes.MONITOREXIT:
        monitorexit();
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitIntInsn(final int opcode, final int operand) {
    switch (opcode) {
      case Opcodes.BIPUSH:
        iconst(operand);
        break;
      case Opcodes.SIPUSH:
        iconst(operand);
        break;
      case Opcodes.NEWARRAY:
        switch (operand) {
          case Opcodes.T_BOOLEAN:
            newarray(Type.BOOLEAN_TYPE);
            break;
          case Opcodes.T_CHAR:
            newarray(Type.CHAR_TYPE);
            break;
          case Opcodes.T_BYTE:
            newarray(Type.BYTE_TYPE);
            break;
          case Opcodes.T_SHORT:
            newarray(Type.SHORT_TYPE);
            break;
          case Opcodes.T_INT:
            newarray(Type.INT_TYPE);
            break;
          case Opcodes.T_FLOAT:
            newarray(Type.FLOAT_TYPE);
            break;
          case Opcodes.T_LONG:
            newarray(Type.LONG_TYPE);
            break;
          case Opcodes.T_DOUBLE:
            newarray(Type.DOUBLE_TYPE);
            break;
          default:
            throw new IllegalArgumentException();
        }
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitVarInsn(final int opcode, final int var) {
    switch (opcode) {
      case Opcodes.ILOAD:
        load(var, Type.INT_TYPE);
        break;
      case Opcodes.LLOAD:
        load(var, Type.LONG_TYPE);
        break;
      case Opcodes.FLOAD:
        load(var, Type.FLOAT_TYPE);
        break;
      case Opcodes.DLOAD:
        load(var, Type.DOUBLE_TYPE);
        break;
      case Opcodes.ALOAD:
        load(var, OBJECT_TYPE);
        break;
      case Opcodes.ISTORE:
        store(var, Type.INT_TYPE);
        break;
      case Opcodes.LSTORE:
        store(var, Type.LONG_TYPE);
        break;
      case Opcodes.FSTORE:
        store(var, Type.FLOAT_TYPE);
        break;
      case Opcodes.DSTORE:
        store(var, Type.DOUBLE_TYPE);
        break;
      case Opcodes.ASTORE:
        store(var, OBJECT_TYPE);
        break;
      case Opcodes.RET:
        ret(var);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitTypeInsn(final int opcode, final String type) {
    Type t = Type.getObjectType(type);
    switch (opcode) {
      case Opcodes.NEW:
        anew(t);
        break;
      case Opcodes.ANEWARRAY:
        newarray(t);
        break;
      case Opcodes.CHECKCAST:
        checkcast(t);
        break;
      case Opcodes.INSTANCEOF:
        instanceOf(t);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitFieldInsn(final int opcode, final String owner, final String name,
      final String desc) {
    switch (opcode) {
      case Opcodes.GETSTATIC:
        getstatic(owner, name, desc);
        break;
      case Opcodes.PUTSTATIC:
        putstatic(owner, name, desc);
        break;
      case Opcodes.GETFIELD:
        getfield(owner, name, desc);
        break;
      case Opcodes.PUTFIELD:
        putfield(owner, name, desc);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitMethodInsn(final int opcode, final String owner, final String name,
      final String desc) {
    switch (opcode) {
      case Opcodes.INVOKESPECIAL:
        invokespecial(owner, name, desc);
        break;
      case Opcodes.INVOKEVIRTUAL:
        invokevirtual(owner, name, desc);
        break;
      case Opcodes.INVOKESTATIC:
        invokestatic(owner, name, desc);
        break;
      case Opcodes.INVOKEINTERFACE:
        invokeinterface(owner, name, desc);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
    invokedynamic(name, desc, bsm, bsmArgs);
  }

  @Override
  public void visitJumpInsn(final int opcode, final Label label) {
    switch (opcode) {
      case Opcodes.IFEQ:
        ifeq(label);
        break;
      case Opcodes.IFNE:
        ifne(label);
        break;
      case Opcodes.IFLT:
        iflt(label);
        break;
      case Opcodes.IFGE:
        ifge(label);
        break;
      case Opcodes.IFGT:
        ifgt(label);
        break;
      case Opcodes.IFLE:
        ifle(label);
        break;
      case Opcodes.IF_ICMPEQ:
        ificmpeq(label);
        break;
      case Opcodes.IF_ICMPNE:
        ificmpne(label);
        break;
      case Opcodes.IF_ICMPLT:
        ificmplt(label);
        break;
      case Opcodes.IF_ICMPGE:
        ificmpge(label);
        break;
      case Opcodes.IF_ICMPGT:
        ificmpgt(label);
        break;
      case Opcodes.IF_ICMPLE:
        ificmple(label);
        break;
      case Opcodes.IF_ACMPEQ:
        ifacmpeq(label);
        break;
      case Opcodes.IF_ACMPNE:
        ifacmpne(label);
        break;
      case Opcodes.GOTO:
        goTo(label);
        break;
      case Opcodes.JSR:
        jsr(label);
        break;
      case Opcodes.IFNULL:
        ifnull(label);
        break;
      case Opcodes.IFNONNULL:
        ifnonnull(label);
        break;
      default:
        throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitLabel(final Label label) {
    mark(label);
  }

  @Override
  public void visitLdcInsn(final Object cst) {
    if (cst instanceof Integer) {
      int val = ((Integer) cst).intValue();
      iconst(val);
    } else if (cst instanceof Byte) {
      int val = ((Byte) cst).intValue();
      iconst(val);
    } else if (cst instanceof Character) {
      int val = ((Character) cst).charValue();
      iconst(val);
    } else if (cst instanceof Short) {
      int val = ((Short) cst).intValue();
      iconst(val);
    } else if (cst instanceof Boolean) {
      int val = ((Boolean) cst).booleanValue() ? 1 : 0;
      iconst(val);
    } else if (cst instanceof Float) {
      float val = ((Float) cst).floatValue();
      fconst(val);
    } else if (cst instanceof Long) {
      long val = ((Long) cst).longValue();
      lconst(val);
    } else if (cst instanceof Double) {
      double val = ((Double) cst).doubleValue();
      dconst(val);
    } else if (cst instanceof String) {
      aconst(cst);
    } else if (cst instanceof Type) {
      tconst((Type) cst);
    } else if (cst instanceof Handle) {
      hconst((Handle) cst);
    } else {
      throw new IllegalArgumentException();
    }
  }

  @Override
  public void visitIincInsn(final int var, final int increment) {
    iinc(var, increment);
  }

  @Override
  public void visitTableSwitchInsn(final int min, final int max, final Label dflt,
      final Label... labels) {
    tableswitch(min, max, dflt, labels);
  }

  @Override
  public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
    lookupswitch(dflt, keys, labels);
  }

  @Override
  public void visitMultiANewArrayInsn(final String desc, final int dims) {
    multianewarray(desc, dims);
  }

  // -----------------------------------------------------------------------

  public void nop() {
    mv.visitInsn(Opcodes.NOP);
  }

  public void aconst(final Object cst) {
    if (cst == null) {
      mv.visitInsn(Opcodes.ACONST_NULL);
    } else {
      mv.visitLdcInsn(cst);
    }
  }

  public void iconst(final int cst) {
    if (cst >= -1 && cst <= 5) {
      mv.visitInsn(Opcodes.ICONST_0 + cst);
    } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
      mv.visitIntInsn(Opcodes.BIPUSH, cst);
    } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
      mv.visitIntInsn(Opcodes.SIPUSH, cst);
    } else {
      mv.visitLdcInsn(new Integer(cst));
    }
  }

  public void lconst(final long cst) {
    if (cst == 0L || cst == 1L) {
      mv.visitInsn(Opcodes.LCONST_0 + (int) cst);
    } else {
      mv.visitLdcInsn(new Long(cst));
    }
  }

  public void fconst(final float cst) {
    int bits = Float.floatToIntBits(cst);
    if (bits == 0L || bits == 0x3f800000 || bits == 0x40000000) { // 0..2
      mv.visitInsn(Opcodes.FCONST_0 + (int) cst);
    } else {
      mv.visitLdcInsn(new Float(cst));
    }
  }

  public void dconst(final double cst) {
    long bits = Double.doubleToLongBits(cst);
    if (bits == 0L || bits == 0x3ff0000000000000L) { // +0.0d and 1.0d
      mv.visitInsn(Opcodes.DCONST_0 + (int) cst);
    } else {
      mv.visitLdcInsn(new Double(cst));
    }
  }

  public void tconst(final Type type) {
    mv.visitLdcInsn(type);
  }

  public void hconst(final Handle handle) {
    mv.visitLdcInsn(handle);
  }

  public void load(final int var, final Type type) {
    mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), var);
  }

  public void aload(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IALOAD));
  }

  public void store(final int var, final Type type) {
    mv.visitVarInsn(type.getOpcode(Opcodes.ISTORE), var);
  }

  public void astore(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IASTORE));
  }

  public void pop() {
    mv.visitInsn(Opcodes.POP);
  }

  public void pop2() {
    mv.visitInsn(Opcodes.POP2);
  }

  public void dup() {
    mv.visitInsn(Opcodes.DUP);
  }

  public void dup2() {
    mv.visitInsn(Opcodes.DUP2);
  }

  public void dupX1() {
    mv.visitInsn(Opcodes.DUP_X1);
  }

  public void dupX2() {
    mv.visitInsn(Opcodes.DUP_X2);
  }

  public void dup2X1() {
    mv.visitInsn(Opcodes.DUP2_X1);
  }

  public void dup2X2() {
    mv.visitInsn(Opcodes.DUP2_X2);
  }

  public void swap() {
    mv.visitInsn(Opcodes.SWAP);
  }

  public void add(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IADD));
  }

  public void sub(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.ISUB));
  }

  public void mul(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IMUL));
  }

  public void div(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IDIV));
  }

  public void rem(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IREM));
  }

  public void neg(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.INEG));
  }

  public void shl(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.ISHL));
  }

  public void shr(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.ISHR));
  }

  public void ushr(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IUSHR));
  }

  public void and(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IAND));
  }

  public void or(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IOR));
  }

  public void xor(final Type type) {
    mv.visitInsn(type.getOpcode(Opcodes.IXOR));
  }

  public void iinc(final int var, final int increment) {
    mv.visitIincInsn(var, increment);
  }

  public void cast(final Type from, final Type to) {
    if (from != to) {
      if (from == Type.DOUBLE_TYPE) {
        if (to == Type.FLOAT_TYPE) {
          mv.visitInsn(Opcodes.D2F);
        } else if (to == Type.LONG_TYPE) {
          mv.visitInsn(Opcodes.D2L);
        } else {
          mv.visitInsn(Opcodes.D2I);
          cast(Type.INT_TYPE, to);
        }
      } else if (from == Type.FLOAT_TYPE) {
        if (to == Type.DOUBLE_TYPE) {
          mv.visitInsn(Opcodes.F2D);
        } else if (to == Type.LONG_TYPE) {
          mv.visitInsn(Opcodes.F2L);
        } else {
          mv.visitInsn(Opcodes.F2I);
          cast(Type.INT_TYPE, to);
        }
      } else if (from == Type.LONG_TYPE) {
        if (to == Type.DOUBLE_TYPE) {
          mv.visitInsn(Opcodes.L2D);
        } else if (to == Type.FLOAT_TYPE) {
          mv.visitInsn(Opcodes.L2F);
        } else {
          mv.visitInsn(Opcodes.L2I);
          cast(Type.INT_TYPE, to);
        }
      } else {
        if (to == Type.BYTE_TYPE) {
          mv.visitInsn(Opcodes.I2B);
        } else if (to == Type.CHAR_TYPE) {
          mv.visitInsn(Opcodes.I2C);
        } else if (to == Type.DOUBLE_TYPE) {
          mv.visitInsn(Opcodes.I2D);
        } else if (to == Type.FLOAT_TYPE) {
          mv.visitInsn(Opcodes.I2F);
        } else if (to == Type.LONG_TYPE) {
          mv.visitInsn(Opcodes.I2L);
        } else if (to == Type.SHORT_TYPE) {
          mv.visitInsn(Opcodes.I2S);
        }
      }
    }
  }

  public void lcmp() {
    mv.visitInsn(Opcodes.LCMP);
  }

  public void cmpl(final Type type) {
    mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPL : Opcodes.DCMPL);
  }

  public void cmpg(final Type type) {
    mv.visitInsn(type == Type.FLOAT_TYPE ? Opcodes.FCMPG : Opcodes.DCMPG);
  }

  public void ifeq(final Label label) {
    mv.visitJumpInsn(Opcodes.IFEQ, label);
  }

  public void ifne(final Label label) {
    mv.visitJumpInsn(Opcodes.IFNE, label);
  }

  public void iflt(final Label label) {
    mv.visitJumpInsn(Opcodes.IFLT, label);
  }

  public void ifge(final Label label) {
    mv.visitJumpInsn(Opcodes.IFGE, label);
  }

  public void ifgt(final Label label) {
    mv.visitJumpInsn(Opcodes.IFGT, label);
  }

  public void ifle(final Label label) {
    mv.visitJumpInsn(Opcodes.IFLE, label);
  }

  public void ificmpeq(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPEQ, label);
  }

  public void ificmpne(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPNE, label);
  }

  public void ificmplt(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPLT, label);
  }

  public void ificmpge(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPGE, label);
  }

  public void ificmpgt(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPGT, label);
  }

  public void ificmple(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ICMPLE, label);
  }

  public void ifacmpeq(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ACMPEQ, label);
  }

  public void ifacmpne(final Label label) {
    mv.visitJumpInsn(Opcodes.IF_ACMPNE, label);
  }

  public void goTo(final Label label) {
    mv.visitJumpInsn(Opcodes.GOTO, label);
  }

  public void jsr(final Label label) {
    mv.visitJumpInsn(Opcodes.JSR, label);
  }

  public void ret(final int var) {
    mv.visitVarInsn(Opcodes.RET, var);
  }

  public void tableswitch(final int min, final int max, final Label dflt, final Label... labels) {
    mv.visitTableSwitchInsn(min, max, dflt, labels);
  }

  public void lookupswitch(final Label dflt, final int[] keys, final Label[] labels) {
    mv.visitLookupSwitchInsn(dflt, keys, labels);
  }

  public void areturn(final Type t) {
    mv.visitInsn(t.getOpcode(Opcodes.IRETURN));
  }

  public void getstatic(final String owner, final String name, final String desc) {
    mv.visitFieldInsn(Opcodes.GETSTATIC, owner, name, desc);
  }

  public void putstatic(final String owner, final String name, final String desc) {
    mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, name, desc);
  }

  public void getfield(final String owner, final String name, final String desc) {
    mv.visitFieldInsn(Opcodes.GETFIELD, owner, name, desc);
  }

  public void putfield(final String owner, final String name, final String desc) {
    mv.visitFieldInsn(Opcodes.PUTFIELD, owner, name, desc);
  }

  public void invokevirtual(final String owner, final String name, final String desc) {
    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc);
  }

  public void invokespecial(final String owner, final String name, final String desc) {
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc);
  }

  public void invokestatic(final String owner, final String name, final String desc) {
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc);
  }

  public void invokeinterface(final String owner, final String name, final String desc) {
    mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc);
  }

  public void invokedynamic(String name, String desc, Handle bsm, Object[] bsmArgs) {
    mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
  }

  public void anew(final Type type) {
    mv.visitTypeInsn(Opcodes.NEW, type.getInternalName());
  }

  public void newarray(final Type type) {
    int typ;
    switch (type.getSort()) {
      case Type.BOOLEAN:
        typ = Opcodes.T_BOOLEAN;
        break;
      case Type.CHAR:
        typ = Opcodes.T_CHAR;
        break;
      case Type.BYTE:
        typ = Opcodes.T_BYTE;
        break;
      case Type.SHORT:
        typ = Opcodes.T_SHORT;
        break;
      case Type.INT:
        typ = Opcodes.T_INT;
        break;
      case Type.FLOAT:
        typ = Opcodes.T_FLOAT;
        break;
      case Type.LONG:
        typ = Opcodes.T_LONG;
        break;
      case Type.DOUBLE:
        typ = Opcodes.T_DOUBLE;
        break;
      default:
        mv.visitTypeInsn(Opcodes.ANEWARRAY, type.getInternalName());
        return;
    }
    mv.visitIntInsn(Opcodes.NEWARRAY, typ);
  }

  public void arraylength() {
    mv.visitInsn(Opcodes.ARRAYLENGTH);
  }

  public void athrow() {
    mv.visitInsn(Opcodes.ATHROW);
  }

  public void checkcast(final Type type) {
    mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName());
  }

  public void instanceOf(final Type type) {
    mv.visitTypeInsn(Opcodes.INSTANCEOF, type.getInternalName());
  }

  public void monitorenter() {
    mv.visitInsn(Opcodes.MONITORENTER);
  }

  public void monitorexit() {
    mv.visitInsn(Opcodes.MONITOREXIT);
  }

  public void multianewarray(final String desc, final int dims) {
    mv.visitMultiANewArrayInsn(desc, dims);
  }

  public void ifnull(final Label label) {
    mv.visitJumpInsn(Opcodes.IFNULL, label);
  }

  public void ifnonnull(final Label label) {
    mv.visitJumpInsn(Opcodes.IFNONNULL, label);
  }

  public void mark(final Label label) {
    mv.visitLabel(label);
  }
}
